/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#pragma once

/*
============================================================================================================

		MATRIX OPS

	These happen in pace on the matrix and update it's current values

============================================================================================================
*/

struct QMATRIX
{
private:
	float m[4][4];

public:
	QMATRIX (void)
	{
		this->Identity ();
	}

	QMATRIX (float _11, float _12, float _13, float _14, float _21, float _22, float _23, float _24, float _31, float _32, float _33, float _34, float _41, float _42, float _43, float _44)
	{
		this->Load (_11, _12, _13, _14, _21, _22, _23, _24, _31, _32, _33, _34, _41, _42, _43, _44);
	}

	QMATRIX (const QMATRIX &other)
	{
		this->Load (other);
	}

	__inline void Load (float _11, float _12, float _13, float _14, float _21, float _22, float _23, float _24, float _31, float _32, float _33, float _34, float _41, float _42, float _43, float _44)
	{
		this->m[0][0] = _11; this->m[0][1] = _12; this->m[0][2] = _13; this->m[0][3] = _14;
		this->m[1][0] = _21; this->m[1][1] = _22; this->m[1][2] = _23; this->m[1][3] = _24;
		this->m[2][0] = _31; this->m[2][1] = _32; this->m[2][2] = _33; this->m[2][3] = _34;
		this->m[3][0] = _41; this->m[3][1] = _42; this->m[3][2] = _43; this->m[3][3] = _44;
	}

	__inline void Load (const QMATRIX &other)
	{
		this->Load (
			other[0][0], other[0][1], other[0][2], other[0][3],
			other[1][0], other[1][1], other[1][2], other[1][3],
			other[2][0], other[2][1], other[2][2], other[2][3],
			other[3][0], other[3][1], other[3][2], other[3][3]
		);
	}

	__inline void Identity (void)
	{
		this->Load (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
	}

	__inline void Mult (const QMATRIX &m)
	{
		this->Load (
			m[0][0] * this->m[0][0] + m[0][1] * this->m[1][0] + m[0][2] * this->m[2][0] + m[0][3] * this->m[3][0],
			m[0][0] * this->m[0][1] + m[0][1] * this->m[1][1] + m[0][2] * this->m[2][1] + m[0][3] * this->m[3][1],
			m[0][0] * this->m[0][2] + m[0][1] * this->m[1][2] + m[0][2] * this->m[2][2] + m[0][3] * this->m[3][2],
			m[0][0] * this->m[0][3] + m[0][1] * this->m[1][3] + m[0][2] * this->m[2][3] + m[0][3] * this->m[3][3],
			m[1][0] * this->m[0][0] + m[1][1] * this->m[1][0] + m[1][2] * this->m[2][0] + m[1][3] * this->m[3][0],
			m[1][0] * this->m[0][1] + m[1][1] * this->m[1][1] + m[1][2] * this->m[2][1] + m[1][3] * this->m[3][1],
			m[1][0] * this->m[0][2] + m[1][1] * this->m[1][2] + m[1][2] * this->m[2][2] + m[1][3] * this->m[3][2],
			m[1][0] * this->m[0][3] + m[1][1] * this->m[1][3] + m[1][2] * this->m[2][3] + m[1][3] * this->m[3][3],
			m[2][0] * this->m[0][0] + m[2][1] * this->m[1][0] + m[2][2] * this->m[2][0] + m[2][3] * this->m[3][0],
			m[2][0] * this->m[0][1] + m[2][1] * this->m[1][1] + m[2][2] * this->m[2][1] + m[2][3] * this->m[3][1],
			m[2][0] * this->m[0][2] + m[2][1] * this->m[1][2] + m[2][2] * this->m[2][2] + m[2][3] * this->m[3][2],
			m[2][0] * this->m[0][3] + m[2][1] * this->m[1][3] + m[2][2] * this->m[2][3] + m[2][3] * this->m[3][3],
			m[3][0] * this->m[0][0] + m[3][1] * this->m[1][0] + m[3][2] * this->m[2][0] + m[3][3] * this->m[3][0],
			m[3][0] * this->m[0][1] + m[3][1] * this->m[1][1] + m[3][2] * this->m[2][1] + m[3][3] * this->m[3][1],
			m[3][0] * this->m[0][2] + m[3][1] * this->m[1][2] + m[3][2] * this->m[2][2] + m[3][3] * this->m[3][2],
			m[3][0] * this->m[0][3] + m[3][1] * this->m[1][3] + m[3][2] * this->m[2][3] + m[3][3] * this->m[3][3]
		);
	}

	__inline void Mult (const QMATRIX &m1, const QMATRIX &m2)
	{
		this->Load (
			m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0] + m1[0][3] * m2[3][0],
			m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] + m1[0][2] * m2[2][1] + m1[0][3] * m2[3][1],
			m1[0][0] * m2[0][2] + m1[0][1] * m2[1][2] + m1[0][2] * m2[2][2] + m1[0][3] * m2[3][2],
			m1[0][0] * m2[0][3] + m1[0][1] * m2[1][3] + m1[0][2] * m2[2][3] + m1[0][3] * m2[3][3],
			m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0] + m1[1][3] * m2[3][0],
			m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] + m1[1][2] * m2[2][1] + m1[1][3] * m2[3][1],
			m1[1][0] * m2[0][2] + m1[1][1] * m2[1][2] + m1[1][2] * m2[2][2] + m1[1][3] * m2[3][2],
			m1[1][0] * m2[0][3] + m1[1][1] * m2[1][3] + m1[1][2] * m2[2][3] + m1[1][3] * m2[3][3],
			m1[2][0] * m2[0][0] + m1[2][1] * m2[1][0] + m1[2][2] * m2[2][0] + m1[2][3] * m2[3][0],
			m1[2][0] * m2[0][1] + m1[2][1] * m2[1][1] + m1[2][2] * m2[2][1] + m1[2][3] * m2[3][1],
			m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2] + m1[2][3] * m2[3][2],
			m1[2][0] * m2[0][3] + m1[2][1] * m2[1][3] + m1[2][2] * m2[2][3] + m1[2][3] * m2[3][3],
			m1[3][0] * m2[0][0] + m1[3][1] * m2[1][0] + m1[3][2] * m2[2][0] + m1[3][3] * m2[3][0],
			m1[3][0] * m2[0][1] + m1[3][1] * m2[1][1] + m1[3][2] * m2[2][1] + m1[3][3] * m2[3][1],
			m1[3][0] * m2[0][2] + m1[3][1] * m2[1][2] + m1[3][2] * m2[2][2] + m1[3][3] * m2[3][2],
			m1[3][0] * m2[0][3] + m1[3][1] * m2[1][3] + m1[3][2] * m2[2][3] + m1[3][3] * m2[3][3]
		);
	}

	__inline void Mult (const float *m)
	{
		this->Mult (m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[1], m[11], m[12], m[13], m[14], m[15]);
	}

	__inline void Mult (float _11, float _12, float _13, float _14, float _21, float _22, float _23, float _24, float _31, float _32, float _33, float _34, float _41, float _42, float _43, float _44)
	{
		this->Load (
			_11 * this->m[0][0] + _12 * this->m[1][0] + _13 * this->m[2][0] + _14 * this->m[3][0],
			_11 * this->m[0][1] + _12 * this->m[1][1] + _13 * this->m[2][1] + _14 * this->m[3][1],
			_11 * this->m[0][2] + _12 * this->m[1][2] + _13 * this->m[2][2] + _14 * this->m[3][2],
			_11 * this->m[0][3] + _12 * this->m[1][3] + _13 * this->m[2][3] + _14 * this->m[3][3],
			_21 * this->m[0][0] + _22 * this->m[1][0] + _23 * this->m[2][0] + _24 * this->m[3][0],
			_21 * this->m[0][1] + _22 * this->m[1][1] + _23 * this->m[2][1] + _24 * this->m[3][1],
			_21 * this->m[0][2] + _22 * this->m[1][2] + _23 * this->m[2][2] + _24 * this->m[3][2],
			_21 * this->m[0][3] + _22 * this->m[1][3] + _23 * this->m[2][3] + _24 * this->m[3][3],
			_31 * this->m[0][0] + _32 * this->m[1][0] + _33 * this->m[2][0] + _34 * this->m[3][0],
			_31 * this->m[0][1] + _32 * this->m[1][1] + _33 * this->m[2][1] + _34 * this->m[3][1],
			_31 * this->m[0][2] + _32 * this->m[1][2] + _33 * this->m[2][2] + _34 * this->m[3][2],
			_31 * this->m[0][3] + _32 * this->m[1][3] + _33 * this->m[2][3] + _34 * this->m[3][3],
			_41 * this->m[0][0] + _42 * this->m[1][0] + _43 * this->m[2][0] + _44 * this->m[3][0],
			_41 * this->m[0][1] + _42 * this->m[1][1] + _43 * this->m[2][1] + _44 * this->m[3][1],
			_41 * this->m[0][2] + _42 * this->m[1][2] + _43 * this->m[2][2] + _44 * this->m[3][2],
			_41 * this->m[0][3] + _42 * this->m[1][3] + _43 * this->m[2][3] + _44 * this->m[3][3]
		);
	}

	__inline void Ortho (float left, float right, float bottom, float top, float zNear, float zFar)
	{
		this->Mult (
			2 / (right - left),
			0,
			0,
			0,
			0,
			2 / (top - bottom),
			0,
			0,
			0,
			0,
			-2 / (zFar - zNear),
			0,
			-((right + left) / (right - left)),
			-((top + bottom) / (top - bottom)),
			-((zFar + zNear) / (zFar - zNear)),
			1
		);
	}

	__inline void Perspective (float fovx, float fovy, float znear, float zfar)
	{
		this->Mult (
			1.0f / tan (fovx * M_PI / 360.0),
			0,
			0,
			0,
			0,
			1.0f / tan (fovy * M_PI / 360.0),
			0,
			0,
			0,
			0,
			-((zfar + znear) / (zfar - znear)),
			-1,
			0,
			0,
			-(zfar * znear / (zfar - znear)),
			0
		);
	}

	__inline void Perspective (float fovx, float fovy)
	{
		float e = 0.000001f;

		this->Mult (
			(1.0f / tanf (D3DXToRadian (fovx) * 0.5f)),
			0,
			0,
			0,
			0,
			1.0f / tanf (D3DXToRadian (fovy) * 0.5f),
			0,
			0,
			0,
			0,
			-1.0f + e,
			-1.0f,
			0,
			0,
			-4.0f,
			0
		);
	}

	__inline void Translate (float x, float y, float z)
	{
		this->m[3][0] += x * this->m[0][0] + y * this->m[1][0] + z * this->m[2][0];
		this->m[3][1] += x * this->m[0][1] + y * this->m[1][1] + z * this->m[2][1];
		this->m[3][2] += x * this->m[0][2] + y * this->m[1][2] + z * this->m[2][2];
		this->m[3][3] += x * this->m[0][3] + y * this->m[1][3] + z * this->m[2][3];
	}

	__inline void Scale (float x, float y, float z)
	{
		Vector4Scale (this->m[0], this->m[0], x);
		Vector4Scale (this->m[1], this->m[1], y);
		Vector4Scale (this->m[2], this->m[2], z);
	}

	__inline void Rotate (float p, float y, float r)
	{
		float sr = sin (DEG2RAD (r));
		float sp = sin (DEG2RAD (p));
		float sy = sin (DEG2RAD (y));
		float cr = cos (DEG2RAD (r));
		float cp = cos (DEG2RAD (p));
		float cy = cos (DEG2RAD (y));

		this->Mult (
			(cp * cy),
			(cp * sy),
			-sp,
			0.0f,
			(cr * -sy) + (sr * sp * cy),
			(cr * cy) + (sr * sp * sy),
			(sr * cp),
			0.0f,
			(sr * sy) + (cr * sp * cy),
			(-sr * cy) + (cr * sp * sy),
			(cr * cp),
			0.0f,
			0.0f,
			0.0f,
			0.0f,
			1.0f
		);
	}

	__inline void Transpose (void)
	{
		this->Load (
			this->m[0][0], this->m[1][0], this->m[2][0], this->m[3][0],
			this->m[0][1], this->m[1][1], this->m[2][1], this->m[3][1],
			this->m[0][2], this->m[1][2], this->m[2][2], this->m[3][2],
			this->m[0][3], this->m[1][3], this->m[2][3], this->m[3][3]
		);
	}

	__inline void Camera (const float *origin, const float *angles)
	{
		float sr = sin (DEG2RAD (angles[2]));
		float sp = sin (DEG2RAD (angles[0]));
		float sy = sin (DEG2RAD (angles[1]));
		float cr = cos (DEG2RAD (angles[2]));
		float cp = cos (DEG2RAD (angles[0]));
		float cy = cos (DEG2RAD (angles[1]));

		float _11 = -((cr * -sy) + (sr * sp * cy));
		float _21 = -((cr * cy) + (sr * sp * sy));
		float _31 = -(sr * cp);

		float _12 = (sr * sy) + (cr * sp * cy);
		float _22 = (-sr * cy) + (cr * sp * sy);
		float _32 = (cr * cp);

		float _13 = -(cp * cy);
		float _23 = -(cp * sy);
		float _33 = sp;

		this->Mult (
			_11, _12, _13, 0.0f,
			_21, _22, _23, 0.0f,
			_31, _32, _33, 0.0f,
			-origin[0] * _11 - origin[1] * _21 - origin[2] * _31,
			-origin[0] * _12 - origin[1] * _22 - origin[2] * _32,
			-origin[0] * _13 - origin[1] * _23 - origin[2] * _33,
			1.0f
		);
	}

	__inline void Billboard (const float *origin, const float *angles)
	{
		// build our rotation matrix
		this->Identity ();
		this->Camera (vec3_origin, angles);

		// the billboard matrix is the transpose rotation with the new origin baked in
		this->Load (
			this->m[0][0], this->m[1][0], this->m[2][0], 0,
			this->m[0][1], this->m[1][1], this->m[2][1], 0,
			this->m[0][2], this->m[1][2], this->m[2][2], 0,
			origin[0], origin[1], origin[2], 1
		);
	}

	__inline void TransformH (float *out, const float *in) const
	{
		out[0] = in[0] * this->m[0][0] + in[1] * this->m[1][0] + in[2] * this->m[2][0] + in[3] * this->m[3][0];
		out[1] = in[0] * this->m[0][1] + in[1] * this->m[1][1] + in[2] * this->m[2][1] + in[3] * this->m[3][1];
		out[2] = in[0] * this->m[0][2] + in[1] * this->m[1][2] + in[2] * this->m[2][2] + in[3] * this->m[3][2];
		out[3] = in[0] * this->m[0][3] + in[1] * this->m[1][3] + in[2] * this->m[2][3] + in[3] * this->m[3][3];
	}

	__inline void Transform (float *out, const float *in) const
	{
		out[0] = in[0] * this->m[0][0] + in[1] * this->m[1][0] + in[2] * this->m[2][0] + this->m[3][0];
		out[1] = in[0] * this->m[0][1] + in[1] * this->m[1][1] + in[2] * this->m[2][1] + this->m[3][1];
		out[2] = in[0] * this->m[0][2] + in[1] * this->m[1][2] + in[2] * this->m[2][2] + this->m[3][2];
	}

	__inline void Transform (float *inout) const
	{
		this->Transform (inout, float3 (inout[0], inout[1], inout[2]));
	}

	__inline void PutZGoingUp (void)
	{
		this->Mult (0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1);
	}

	__inline void Rotate (float x, float y, float z, float angle)
	{
		float sa = sin (D3DXToRadian (angle));
		float ca = cos (D3DXToRadian (angle));

		this->Mult (
			(1.0f - ca) * x * x + ca,
			(1.0f - ca) * y * x + sa * z,
			(1.0f - ca) * z * x - sa * y,
			0.0f,
			(1.0f - ca) * x * y - sa * z,
			(1.0f - ca) * y * y + ca,
			(1.0f - ca) * z * y + sa * x,
			0.0f,
			(1.0f - ca) * x * z + sa * y,
			(1.0f - ca) * y * z - sa * x,
			(1.0f - ca) * z * z + ca,
			0.0f,
			0.0f,
			0.0f,
			0.0f,
			1.0f
		);
	}

	__inline void InverseTransform (float *out, const float *in) const
	{
		// http://content.gpwiki.org/index.php/MathGem:Fast_Matrix_Inversion
		out[0] = in[0] * this->m[0][0] + in[1] * this->m[0][1] + in[2] * this->m[0][2] - Vector3Dot (this->m[0], this->m[3]);
		out[1] = in[0] * this->m[1][0] + in[1] * this->m[1][1] + in[2] * this->m[1][2] - Vector3Dot (this->m[1], this->m[3]);
		out[2] = in[0] * this->m[2][0] + in[1] * this->m[2][1] + in[2] * this->m[2][2] - Vector3Dot (this->m[2], this->m[3]);
	}

	__inline const float *Forward (void) const
	{
		return float3 (-this->m[0][2], -this->m[1][2], -this->m[2][2]);
	}

	__inline const float *Up (void) const
	{
		return float3 (this->m[0][1], this->m[1][1], this->m[2][1]);
	}

	__inline const float *Right (void) const
	{
		return float3 (this->m[0][0], this->m[1][0], this->m[2][0]);
	}

	__inline void ToVectors (float *f, float *r, float *u) const
	{
		Vector3Set (f, -this->m[0][2], -this->m[1][2], -this->m[2][2]);
		Vector3Set (r, this->m[0][0], this->m[1][0], this->m[2][0]);
		Vector3Set (u, this->m[0][1], this->m[1][1], this->m[2][1]);
	}

	__inline void AlignCubemapFace (int axis, float *origin)
	{
		switch (axis)
		{
		case 0: this->Mult (0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -origin[2], -origin[1], origin[0], 1); break;
		case 1: this->Mult (0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, origin[2], -origin[1], -origin[0], 1); break;
		case 2: this->Mult (1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, -origin[0], -origin[2], origin[1], 1); break;
		case 3: this->Mult (1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, -origin[0], origin[2], -origin[1], 1); break;
		case 4: this->Mult (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -origin[0], -origin[1], -origin[2], 1); break;
		case 5: this->Mult (-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, origin[0], -origin[1], origin[2], 1); break;
		};
	}

	__inline void Project (float *out, const float *in, float width, float height) const
	{
		float w = in[0] * this->m[0][3] + in[1] * this->m[1][3] + in[2] * this->m[2][3] + this->m[3][3];

		out[0] = (1.0f + ((in[0] * this->m[0][0] + in[1] * this->m[1][0] + in[2] * this->m[2][0] + this->m[3][0]) / w)) * width / 2.0f;
		out[1] = (1.0f - ((in[0] * this->m[0][1] + in[1] * this->m[1][1] + in[2] * this->m[2][1] + this->m[3][1]) / w)) * height / 2.0f;
		out[2] = (in[0] * this->m[0][2] + in[1] * this->m[1][2] + in[2] * this->m[2][2] + this->m[3][2]) / w;
	}

	__inline void ToFrustum (mplane_t *frustum, const float *vieworigin) const
	{
		Vector3Set (frustum[0].normal, this->m[0][3] - this->m[0][0], this->m[1][3] - this->m[1][0], this->m[2][3] - this->m[2][0]);
		Vector3Set (frustum[1].normal, this->m[0][3] + this->m[0][0], this->m[1][3] + this->m[1][0], this->m[2][3] + this->m[2][0]);
		Vector3Set (frustum[2].normal, this->m[0][3] + this->m[0][1], this->m[1][3] + this->m[1][1], this->m[2][3] + this->m[2][1]);
		Vector3Set (frustum[3].normal, this->m[0][3] - this->m[0][1], this->m[1][3] - this->m[1][1], this->m[2][3] - this->m[2][1]);

		for (int i = 0; i < 4; i++)
		{
			Vector3Normalize (frustum[i].normal);

			frustum[i].dist = Vector3Dot (vieworigin, frustum[i].normal);
			frustum[i].signbits = SignbitsForPlane (&frustum[i]);
		}
	}

	__inline const float *operator[] (const int index) const
	{
		return this->m[index];
	}

	__inline void Get (float *dst) const
	{
		Vector4Copy (&dst[0], this->m[0]);
		Vector4Copy (&dst[4], this->m[1]);
		Vector4Copy (&dst[8], this->m[2]);
		Vector4Copy (&dst[12], this->m[3]);
	}

	__inline void ToD3DX (D3DXMATRIX &out) const
	{
		Vector4Copy (out.m[0], this->m[0]);
		Vector4Copy (out.m[1], this->m[1]);
		Vector4Copy (out.m[2], this->m[2]);
		Vector4Copy (out.m[3], this->m[3]);
	}

	__inline void FromD3DX (const D3DXMATRIX &in)
	{
		Vector4Copy (this->m[0], in.m[0]);
		Vector4Copy (this->m[1], in.m[1]);
		Vector4Copy (this->m[2], in.m[2]);
		Vector4Copy (this->m[3], in.m[3]);
	}
};

